#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace Osh
{
	/// <summary>
	/// Provides synchronous pipe packet stream.
	/// </summary>
	public class PipePacketStream : PacketStream
	{
		#region Native API
		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		public static extern IntPtr CreateFile(string fileName, uint desiredAccess, uint shareMode,
			IntPtr securityAttributes, uint creationDisposition, uint flagsAndAttributes, IntPtr templateFile);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		//private static extern unsafe bool ReadFile(IntPtr handle,
		//    byte* bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped);
		private static extern unsafe bool ReadFile(IntPtr handle,
			byte* bytes, int numBytesToRead, out int numBytesRead, NativeOverlapped* overlapped);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern unsafe bool GetOverlappedResult(IntPtr handle, NativeOverlapped* overlapped,
			out int numBytesRead, bool wait);
		#endregion Native API

		private const int DefaultBufferSize = 100;
		private ManualResetEvent _stopEvent = new ManualResetEvent(false);
		private ManualResetEvent _readCompleteEvent = new ManualResetEvent(false);
		private IntPtr _pipeHandle;
		private string _pipeName;

		public PipePacketStream(string pipeName)
		{
			_pipeName = pipeName;
			string fullName = @"\\.\pipe\" + pipeName;
			_pipeHandle = CreateFile(fullName, Win32.GENERIC_READ + Win32.GENERIC_WRITE, 0, IntPtr.Zero,
				Win32.OPEN_EXISTING, Win32.FILE_FLAG_OVERLAPPED, IntPtr.Zero);
			if((int)_pipeHandle == Win32.INVALID_HANDLE_VALUE)
				Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

			uint mode = Win32.PIPE_READMODE_MESSAGE;
			if(!Win32.SetNamedPipeHandleState(_pipeHandle, ref mode, IntPtr.Zero, IntPtr.Zero))
				Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

			this.PacketBuffer = new byte[DefaultBufferSize];
		}

		public string PipeName
		{
			get { return _pipeName; }
		}

		~PipePacketStream()
		{
			if(_pipeHandle != IntPtr.Zero)
				this.Dispose(false);
		}

		#region PacketStreamBase Overrides
		protected override unsafe bool MoveToNextPacketInternal()
		{
			bool result;
			int lastWin32Error;
			int messageBufferOffset = 0;
			base.PacketSize = 0;
			do
			{
				int numBytesRead = 0;

				fixed(byte* bufferPtr = this.PacketBuffer)
				{
					Overlapped overlapped = new Overlapped(0, 0,
						_readCompleteEvent.SafeWaitHandle.DangerousGetHandle(), null);
					NativeOverlapped* overlappedPtr = overlapped.UnsafePack(null, null);
					try
					{
						result = ReadFile(_pipeHandle, bufferPtr + messageBufferOffset,
							this.PacketBuffer.Length - messageBufferOffset, out numBytesRead, overlappedPtr);
						lastWin32Error = Marshal.GetLastWin32Error();

						if(!result)
						{
							if(lastWin32Error == Win32.ERROR_BROKEN_PIPE)
								return false;

							if(lastWin32Error == Win32.ERROR_IO_PENDING)
							{
								int eventIndex = WaitHandle.WaitAny(new WaitHandle[] { _readCompleteEvent, _stopEvent });
								if(eventIndex == 1)
									return false;
							}
							else if(lastWin32Error != Win32.ERROR_MORE_DATA)
								throw new IOException("", Win32.GetHRForWin32Error(lastWin32Error));

							result = GetOverlappedResult(_pipeHandle, overlappedPtr, out numBytesRead, false);
							lastWin32Error = Marshal.GetLastWin32Error();
						}
					}
					finally
					{
						if(overlappedPtr != null)
							Overlapped.Free(overlappedPtr);
					}
				}

				if(!result)
				{
					if(lastWin32Error != Win32.ERROR_MORE_DATA)
						throw new IOException("", Win32.GetHRForWin32Error(lastWin32Error));

					// Allocate a bigger array and copy the read data into it.
					byte[] newBuffer = new byte[this.PacketBuffer.Length + DefaultBufferSize];
					Buffer.BlockCopy(this.PacketBuffer, 0, newBuffer, 0, this.PacketBuffer.Length);
					messageBufferOffset = this.PacketBuffer.Length;
					this.PacketBuffer = newBuffer;
				}
				this.PacketSize += numBytesRead;
			}
			while(!result);

			return _pipeHandle != IntPtr.Zero;
		}

		protected override void Dispose(bool disposing)
		{
			IntPtr pipeHandle = _pipeHandle;
			if(pipeHandle != IntPtr.Zero)
			{
				_pipeHandle = IntPtr.Zero;
				if(disposing)
					_stopEvent.Set();
				Win32.CloseHandle(pipeHandle);
			}

			if(disposing)
			{
				_readCompleteEvent.Close();
				_stopEvent.Close();
			}

			base.Dispose(disposing);
		}
		#endregion PacketStreamBase Overrides
	}
}
